using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace Gko.Utils
{
	/// <summary>
	/// Base class for wrapping unmanaged interface pointer
	/// </summary>
	public abstract class UnmanagedInterface : DisposableBase
	{
		internal readonly Dictionary<Type, object> methods;

		internal NativeVFTable nativeVirtualFunctionsTable;

		/// <summary>
		/// Initializes a new instance of the <see cref="T:Gko.Utils.UnmanagedInterface"/> class.
		/// </summary>
		/// <param name="unsafeHandle">The unsafe handle.</param>
		/// <exception cref="ArgumentNullException">Throws when <c>unsafeHandle</c> is null.</exception>
		protected UnmanagedInterface(IntPtr unsafeHandle)
		{
			if (unsafeHandle.IsNull()) {
				throw new ArgumentNullException("unsafeHandle");
			}
			this.UnsafeHandle = unsafeHandle;
			this.methods = new Dictionary<Type, object>();
			this.nativeVirtualFunctionsTable = new NativeVFTable(this.UnsafeHandle.ToStructure<NativeInterface>());
		}

		/// <summary>
		/// Gets or sets the unsafe handle.
		/// </summary>
		/// <value>The unsafe handle.</value>
		public IntPtr UnsafeHandle
		{
			get;
			protected set;
		}

		/// <summary>
		/// Initializes virtual function table by the specified delegate types.
		/// </summary>
		/// <param name="delegateTypes">The delegate types.</param>
		protected void Init(params Type[] delegateTypes)
		{
			var count = this.methods.Count;
			for (var index = 0; index < delegateTypes.Length; index++) {
				var delegateType = delegateTypes[index];
				var method = this.nativeVirtualFunctionsTable.GetMethod(index + count, delegateType);
				this.InternalAddMethod(delegateType, method);
			}
		}

		private void InternalAddMethod(Type delegateType, object method)
		{
			this.methods.Add(delegateType, method);
		}

		internal void InternalAddMethod<TDelegate>(TDelegate method) where TDelegate : class
		{
			this.InternalAddMethod(typeof(TDelegate), method);
		}

		/// <summary>
		/// Gets the method.
		/// </summary>
		/// <typeparam name="TDelegate">The type of the delegate.</typeparam>
		/// <returns>Delegate</returns>
		/// <exception cref="MissingMethodException"><c>MissingMethodException</c>.</exception>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		protected TDelegate GetMethod<TDelegate>() where TDelegate : class
		{
			var type = typeof(TDelegate);
			if (this.methods.ContainsKey(type)) {
				return (TDelegate)this.methods[type];
			}
			Trace.WriteLine("Method missing {0} on type {1}".Format(type, this.GetType()));
			Trace.WriteLine("Available Methods: ({0})".Format(this.methods.Count));
			foreach (var key in this.methods.Keys) {
				Trace.WriteLine(" {0}".Format(key));
			}
			throw new MissingMethodException(this.GetType().ToString(), type.ToString());
		}
	}
}